All files / web/src/app/admin/tts-lab/bounce/[step] page.tsx

0% Statements 0/126
0% Branches 0/1
0% Functions 0/1
0% Lines 0/126

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127                                                                                                                                                                                                                                                             
'use client'

import { useEffect, useRef } from 'react'
import { useParams, useRouter } from 'next/navigation'
import { useAudioManagerInstance } from '@/contexts/AudioManagerContext'
import { useTTS } from '@/hooks/useTTS'
import {
  STRESS_PROBLEMS,
  readNavState,
  writeNavState,
  appendNavLog,
  describeSegments,
} from '@/components/admin/tts-lab/stressProblems'
import { css } from '../../../../../../styled-system/css'

export default function BounceStepPage() {
  const router = useRouter()
  const params = useParams()
  const step = parseInt(params.step as string, 10)
  const manager = useAudioManagerInstance()
  const ranRef = useRef(false)

  // Register this step's problem with useTTS — mirrors real practice components
  const problem = STRESS_PROBLEMS[step % STRESS_PROBLEMS.length]
  const speak = useTTS(problem, { tone: 'stress-test' })

  useEffect(() => {
    if (ranRef.current) return
    ranRef.current = true

    const state = readNavState()
    if (!state || !state.running) {
      router.replace('/admin/tts-lab')
      return
    }

    const snapshot = manager.getSnapshot()
    const collection = manager.getCollection()

    let s = appendNavLog(state, 'info', `--- Step ${step + 1}/${state.totalSteps} ---`)
    s = appendNavLog(
      s,
      snapshot.isPlaying ? 'warn' : 'info',
      `Manager: playing=${snapshot.isPlaying} enabled=${snapshot.isEnabled} collection=${collection.length}`
    )

    // Check if browser speechSynthesis is still going from the previous page
    if (typeof window !== 'undefined' && 'speechSynthesis' in window) {
      const ss = window.speechSynthesis
      if (ss.speaking || ss.pending) {
        s = appendNavLog(
          s,
          'warn',
          `speechSynthesis leaked: speaking=${ss.speaking} pending=${ss.pending}`
        )
      }
    }

    s = appendNavLog(s, 'info', `Speaking [${describeSegments(problem)}] (${problem.length} segs)`)
    s = { ...s, currentStep: step }
    writeNavState(s)

    // Fire speak — don't await, we'll navigate mid-playback
    const t0 = performance.now()
    speak().then(
      () => {
        const elapsed = (performance.now() - t0).toFixed(0)
        const s2 = readNavState()
        if (s2?.running) {
          writeNavState(appendNavLog(s2, 'info', `speak() resolved (${elapsed}ms)`))
        }
      },
      () => {}
    )

    // Navigate mid-speech
    const isLast = step + 1 >= state.totalSteps
    const dest = isLast ? '/admin/tts-lab' : `/admin/tts-lab/bounce/${step + 1}`

    const timeout = setTimeout(() => {
      const s2 = readNavState()
      if (!s2?.running) return

      const snap = manager.getSnapshot()
      const updated = appendNavLog(s2, 'info', `Navigating → ${dest} (playing=${snap.isPlaying})`)
      writeNavState(updated)
      router.push(dest)
    }, state.interruptDelayMs)

    return () => clearTimeout(timeout)
  }, [manager, speak, router, step, problem])

  const state = readNavState()

  return (
    <div
      className={css({
        backgroundColor: '#0d1117',
        color: '#8b949e',
        padding: '24px',
        minHeight: '100vh',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        gap: '8px',
        fontFamily: 'monospace',
        fontSize: '14px',
      })}
    >
      <div className={css({ color: '#f0f6fc', fontSize: '16px' })}>
        Step {step + 1} / {state?.totalSteps ?? '?'}
      </div>
      <div
        className={css({
          fontSize: '11px',
          color: '#484f58',
          maxWidth: '400px',
          textAlign: 'center',
        })}
      >
        Speaking {problem.length} segments + navigating in {state?.interruptDelayMs ?? '?'}ms...
      </div>
    </div>
  )
}